package com.stars.easyms.datasource.mybatis.transaction;

import com.stars.easyms.datasource.EasyMsDataSource;
import com.stars.easyms.datasource.EasyMsMultiDataSource;
import com.stars.easyms.datasource.holder.EasyMsSynchronizationManager;
import com.stars.easyms.datasource.transaction.EasyMsTransactionStatusHolder;
import com.stars.easyms.datasource.transaction.EasyMsTransactionSynchronizationManager;
import com.stars.easyms.datasource.util.EasyMsDataSourceUtil;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.transaction.Transaction;
import org.springframework.jdbc.datasource.ConnectionHolder;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>className: EasyMsSpringManagedTransaction</p>
 * <p>description: EasyMs自定义事务类</p>
 *
 * @author guoguifang
 * @date 2019-10-15 16:00
 * @since 1.3.3
 */
@Slf4j
final class EasyMsSpringManagedTransaction implements Transaction {

    private EasyMsMultiDataSource easyMsMultiDataSource;

    private Map<EasyMsDataSource, EasyMsConnectionHolder> connectionMap;

    private EasyMsDataSource easyMsDataSource;

    private EasyMsConnectionHolder easyMsConnectionHolder;

    EasyMsSpringManagedTransaction(@NonNull EasyMsMultiDataSource easyMsMultiDataSource) {
        this.easyMsMultiDataSource = easyMsMultiDataSource;
        this.connectionMap = new ConcurrentHashMap<>(8);
    }

    EasyMsSpringManagedTransaction(@NonNull EasyMsDataSource easyMsDataSource) {
        this.easyMsDataSource = easyMsDataSource;
        this.easyMsConnectionHolder = new EasyMsConnectionHolder();
    }

    @Override
    public Connection getConnection() throws SQLException {
        // 获取EasyMsConnectionHolder对象
        EasyMsConnectionHolder localEasyMsConnectionHolder;
        if (this.easyMsDataSource != null) {
            localEasyMsConnectionHolder = this.easyMsConnectionHolder;
            EasyMsTransactionStatusHolder easyMsTransactionStatusHolder =
                    EasyMsTransactionSynchronizationManager.newTransactionStatusHolder(this.easyMsDataSource);
            localEasyMsConnectionHolder.connectionHolder = easyMsTransactionStatusHolder.getConnectionHolder();
            localEasyMsConnectionHolder.connection = localEasyMsConnectionHolder.connectionHolder != null ?
                    localEasyMsConnectionHolder.connectionHolder.getConnection() :
                    EasyMsDataSourceUtil.getConnection(this.easyMsDataSource);

            // spring事务下connection关闭自动提交，是否开启事务为true
            localEasyMsConnectionHolder.autoCommit = false;
            localEasyMsConnectionHolder.isConnectionTransactional = true;
        } else {
            EasyMsDataSource localEasyMsDataSource = getEasyMsDataSource();
            // 正常情况下是单线程执行，在分页的时候使用了多线程，因此需要加锁保证分页的时候可以得到相同的数据库连接
            localEasyMsConnectionHolder = this.connectionMap.computeIfAbsent(localEasyMsDataSource, e -> new EasyMsConnectionHolder());
            // 如果有连接则返回
            if (localEasyMsConnectionHolder.connection != null) {
                return localEasyMsConnectionHolder.connection;
            }
            localEasyMsConnectionHolder.connection = EasyMsDataSourceUtil.getConnection(localEasyMsDataSource);
            localEasyMsConnectionHolder.autoCommit = localEasyMsConnectionHolder.connection.getAutoCommit();
            localEasyMsConnectionHolder.isConnectionTransactional = false;
        }
        if (log.isDebugEnabled()) {
            log.debug("JDBC Connection [{}] will {} be managed by Spring!", localEasyMsConnectionHolder.connection,
                    localEasyMsConnectionHolder.isConnectionTransactional ? "" : "not");
        }
        return localEasyMsConnectionHolder.connection;
    }

    @Override
    public void commit() throws SQLException {
        EasyMsConnectionHolder connectionHolder = this.easyMsDataSource != null ?
                this.easyMsConnectionHolder : this.connectionMap.get(getEasyMsDataSource());
        if (connectionHolder != null && connectionHolder.connection != null
                && !connectionHolder.isConnectionTransactional && !connectionHolder.autoCommit) {
            if (log.isDebugEnabled()) {
                log.debug("Committing JDBC Connection [{}]", connectionHolder.connection);
            }
            connectionHolder.connection.commit();
        }
    }

    @Override
    public void rollback() throws SQLException {
        EasyMsConnectionHolder connectionHolder = this.easyMsDataSource != null ?
                this.easyMsConnectionHolder : this.connectionMap.get(getEasyMsDataSource());
        if (connectionHolder != null && connectionHolder.connection != null
                && !connectionHolder.isConnectionTransactional && !connectionHolder.autoCommit) {
            if (log.isDebugEnabled()) {
                log.debug("Rolling back JDBC Connection [{}]", connectionHolder.connection);
            }
            connectionHolder.connection.rollback();
        }
    }

    @Override
    public void close() {
        EasyMsConnectionHolder localEasyMsConnectionHolder;
        if (this.easyMsDataSource != null) {
            localEasyMsConnectionHolder = this.easyMsConnectionHolder;
            if (localEasyMsConnectionHolder.connectionHolder != null) {
                localEasyMsConnectionHolder.connectionHolder.released();
            }
            return;
        }

        localEasyMsConnectionHolder = this.connectionMap.get(getEasyMsDataSource());
        if (localEasyMsConnectionHolder != null && localEasyMsConnectionHolder.connection != null) {
            if (log.isDebugEnabled()) {
                log.debug("Returning JDBC Connection to DataSource");
            }
            try {
                localEasyMsConnectionHolder.connection.close();
            } catch (SQLException ex) {
                log.error("Could not close JDBC Connection", ex);
            } catch (Exception ex) {
                log.error("Unexpected exception on closing JDBC Connection", ex);
            }
        }
    }

    @Override
    public Integer getTimeout() {
        EasyMsConnectionHolder localEasyMsConnectionHolder = this.easyMsDataSource != null ?
                this.easyMsConnectionHolder : this.connectionMap.get(getEasyMsDataSource());
        if (localEasyMsConnectionHolder != null
                && localEasyMsConnectionHolder.connectionHolder != null
                && localEasyMsConnectionHolder.connectionHolder.hasTimeout()) {
            return localEasyMsConnectionHolder.connectionHolder.getTimeToLiveInSeconds();
        }
        return null;
    }

    private EasyMsDataSource getEasyMsDataSource() {
        EasyMsDataSource localEasyMsDataSource = EasyMsSynchronizationManager.getEasyMsDataSource();
        if (localEasyMsDataSource == null) {
            localEasyMsDataSource = this.easyMsMultiDataSource.getCurrentDataSource();
            if (EasyMsSynchronizationManager.isSynchronization()) {
                EasyMsSynchronizationManager.setEasyMsDataSource(localEasyMsDataSource);
            }
        }
        return localEasyMsDataSource;
    }

    private static class EasyMsConnectionHolder {

        private Connection connection;

        private ConnectionHolder connectionHolder;

        private boolean isConnectionTransactional;

        private boolean autoCommit;
    }
}